package org.zh.sh.util.http;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.TypeReference;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.logging.Level;

import static org.zh.sh.config.GlobalStatic.*;

public class ContextHttpHandler  implements HttpHandler,HttpStatic {
    private static final Set<Type> simpleTypeCache = new HashSet<>();

    private static final String delete = "delete_id";

    private static final Class<?> exchangeClass = HttpExchange.class;
    private static final Class<?> headersClass = Headers.class;
    private static final Class<?> contextClass = HttpContext.class;
    private static final Class<?> mapClass = Map.class;
    private static final Class<?> bodyClass = InputStream.class;

    static {
        simpleTypeCache.add(String.class);
        simpleTypeCache.add(Byte.class);
        simpleTypeCache.add(Short.class);
        simpleTypeCache.add(Character.class);
        simpleTypeCache.add(Integer.class);
        simpleTypeCache.add(Long.class);
        simpleTypeCache.add(Float.class);
        simpleTypeCache.add(Double.class);
        simpleTypeCache.add(Boolean.class);
        simpleTypeCache.add(BigInteger.class);
        simpleTypeCache.add(BigDecimal.class);
        simpleTypeCache.add(byte[].class);
    }

//response.setHeader("Set-Cookie", "loginMsg=" + URLEncoder.encode(jsonString, "UTF-8") + "; Max-Age=604800; Path=/"; HttpOnly);

    private static Object simpleTypeParam(Type param, byte[] bytes) {
        if (param.equals(String.class)) {
            return new String(bytes);
        }
        if (param.equals(Long.class)) {
            return Long.valueOf(new String(bytes));
        }
        if (param.equals(Integer.class)) {
            return Integer.valueOf(new String(bytes));
        }
        if (param.equals(Boolean.class)) {
            return Boolean.valueOf(new String(bytes));
        }
        if (param.equals(BigDecimal.class)) {
            return new BigDecimal(new String(bytes));
        }
        if (param.equals(Float.class)) {
            return Float.valueOf(new String(bytes));
        }
        if (param.equals(Double.class)) {
            return Double.valueOf(new String(bytes));
        }
        if (param.equals(BigInteger.class)) {
            return new BigInteger(bytes);
        }
        if (param.equals(byte[].class)) {
            return bytes;
        }
        if (param.equals(Byte.class)) {
            return bytes[0];
        }
        if (param.equals(Short.class)) {
            return Short.valueOf(new String(bytes));
        }
        if (param.equals(Character.class)) {
            return (char) bytes[0];
        }
        return null;
    }

    @Override
    public void handle(HttpExchange ex) {

        try {
            if (doStaticFile(ex)) return;
            if (doGET(ex)) return;
            if (doJSON(ex)) return;
            if (doMultipart(ex)) return;
            if (doOldFORM(ex)) return;
            if (doTEXT(ex)) return;
            if (doHead(ex)) return;
            if (doDelete(ex)) return;
            OutputStream responseBody = ex.getResponseBody();
            ex.getResponseHeaders().set(contentType, TEXT_PLAIN_VALUE+charset);
            ex.sendResponseHeaders(404, error404.length);
            responseBody.write(error404);
            responseBody.flush();
        } catch (Throwable e) {
            try {
                OutputStream responseBody = ex.getResponseBody();
                ex.getResponseHeaders().set(contentType, TEXT_PLAIN_VALUE+charset);
                ex.sendResponseHeaders(500, error500.length);
                responseBody.write(error500);
                responseBody.flush();
            } catch (IOException exception) {
                log.log(Level.SEVERE, "服务器发送错误信息失败", exception);
            }
            log.log(Level.SEVERE, "服务器处理请求出现错误", e);
        }

    }

    private boolean doDelete(HttpExchange ex) throws Throwable {
        String httpMethod = ex.getRequestMethod();
        if (httpMethod.equals(HttpMethod.DELETE.value())) {
            String path = ex.getRequestURI().getPath();
            int index = path.lastIndexOf("/");
            PathBean pathBean = vllCache.get(path.substring(0, index));
            if (pathBean == null || !pathBean.getHttpMethod().equals(HttpMethod.DELETE.value())) {
                error405( ex);
                return true;
            }
            List<Object> paramsO = new ArrayList<>();
            Type[] parameterTypes = pathBean.getTypes();
            String query = ex.getRequestURI().getQuery();
            Map<String, Object> params = queryStringDecoder(query, true);
            String delete_ = path.substring(index + 1);
            params.put(delete,delete_ );
            for (Type param : parameterTypes) {
                if (simpleTypeCache.contains(param)) {
                    paramsO.add(simpleTypeParam(param, delete_.getBytes(StandardCharsets.UTF_8)));
                } else if (param.equals(mapClass)) {
                    paramsO.add(params);
                } else if (param.equals(headersClass)) {
                    paramsO.add(ex.getRequestHeaders());
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else {
                    throw new RuntimeException("HEAD请求 只能有没有请求体三种参数 com.sun.net.httpserver.Headers ,com.sun.net.httpserver.HttpExchange,com.sun.net.httpserver.HttpContext");
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }



    private boolean doHead(HttpExchange ex) throws Throwable {
        String httpMethod = ex.getRequestMethod();
        if (httpMethod.equals(HttpMethod.HEAD.value())) {
            String path = ex.getRequestURI().getPath();
            PathBean pathBean = vllCache.get(path);
            if (pathBean == null || !pathBean.getHttpMethod().equals(HttpMethod.HEAD.value())) {
                error405( ex);
                return true;
            }
            List<Object> paramsO = new ArrayList<>();
            Type[] parameterTypes = pathBean.getTypes();
            String query = ex.getRequestURI().getQuery();
            Map<String, Object> params = queryStringDecoder(query, true);
            for (Type param : parameterTypes) {
                if (param.equals(headersClass)) {
                    paramsO.add(ex.getRequestHeaders());
                } else if (param.equals(mapClass)) {
                    paramsO.add(params);
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else {
                    throw new RuntimeException("HEAD请求 只能有没有请求体三种参数 com.sun.net.httpserver.Headers ,com.sun.net.httpserver.HttpExchange,com.sun.net.httpserver.HttpContext");
                }
            }
            pathBean.getMethodHandle().invokeWithArguments(paramsO);
            ex.sendResponseHeaders(204, -1);
            return true;
        }
        return false;
    }

    private boolean doStaticFile(HttpExchange ex) throws IOException {
        String path = ex.getRequestURI().getPath();
        String httpMethod = ex.getRequestMethod();
        OutputStream responseBody = ex.getResponseBody();
        if (httpMethod.equals(HttpMethod.GET.value())) {
            if (staticFile.containsKey(path)) {
                byte[] bytes = staticFile.get(path);
                success200(ex, TEXT_HTML_VALUE+charset,bytes);
                return true;
            }
            if (vllCache.get(path) == null) {
                byte[] bytes = staticFile.get("/index.html");
                success200(ex, TEXT_HTML_VALUE+charset,bytes);
                return true;
            }
        } else {
            if (vllCache.get(path) == null) {
                ex.getResponseHeaders().set(contentType, TEXT_PLAIN_VALUE+charset);
                ex.sendResponseHeaders(404, error404.length);
                responseBody.write(error404);
                responseBody.flush();
                return true;
            }
        }
        return false;
    }

    private boolean doGET(HttpExchange ex) throws Throwable {
        String httpMethod = ex.getRequestMethod();
        String path = ex.getRequestURI().getPath();
        PathBean pathBean = vllCache.get(path);
        if (httpMethod.equals(HttpMethod.GET.value())) {
            if (!pathBean.getHttpMethod().equals(httpMethod)) {
                error405( ex);
                return true;
            }
            Type[] parameterTypes = pathBean.getTypes();
            List<Object> paramsO = new ArrayList<>();
            String query = ex.getRequestURI().getQuery();
            Map<String, Object> params = queryStringDecoder(query, true);
            for (Type param : parameterTypes) {
                if (param.equals(mapClass)) {
                    paramsO.add(params);
                } else if (param.equals(headersClass)) {
                    paramsO.add(ex.getRequestHeaders());
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else {
                    throw new RuntimeException("GET请求  只支持map类型接收参数");
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }

    private boolean doJSON(HttpExchange ex) throws Throwable {
        Headers requestHeaders = ex.getRequestHeaders();
        String ContentType = requestHeaders.getFirst(contentType);
        if (ContentType.equals(APPLICATION_JSON_VALUE) || ContentType.equals(APPLICATION_JSON_UTF8_VALUE)) {
            String path = ex.getRequestURI().getPath();
            String httpMethod = ex.getRequestMethod();
            PathBean pathBean = vllCache.get(path);
            if (!(ContentType.equals(pathBean.getConsumes()) && httpMethod.equals(pathBean.getHttpMethod()))) {
                error405( ex);
                return true;
            }
            Type[] parameterTypes = pathBean.getTypes();
            List<Object> paramsO = new ArrayList<>();
            InputStream requestBody = ex.getRequestBody();
            byte[] bytes = requestBody.readAllBytes();
            for (Type param : parameterTypes) {
                if (param.equals(headersClass)) {
                    paramsO.add(requestHeaders);
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else if (simpleTypeCache.contains(param)) {
                    paramsO.add(simpleTypeParam(param, bytes));
                } else {
                    paramsO.add(JSON.parseObject(bytes, param, JSONReader.Feature.UseLongForInts));
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }

    private boolean doTEXT(HttpExchange ex) throws Throwable {
        Headers requestHeaders = ex.getRequestHeaders();
        String ContentType = requestHeaders.getFirst(contentType);
        if (ContentType.startsWith("text")) {
            String path = ex.getRequestURI().getPath();
            PathBean pathBean = vllCache.get(path);
            String httpMethod = ex.getRequestMethod();
            if (!(ContentType.equals(pathBean.getConsumes()) && httpMethod.equals(pathBean.getHttpMethod()))) {
                error405( ex);
                return true;
            }
            Type[] parameterTypes = pathBean.getTypes();
            InputStream requestBody = ex.getRequestBody();
            List<Object> paramsO = new ArrayList<>();
            for (Type param : parameterTypes) {
                if (param.equals(headersClass)) {
                    paramsO.add(requestHeaders);
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else if (param.equals(bodyClass)) {
                    paramsO.add(requestBody);
                } else if (simpleTypeCache.contains(param)) {
                    byte[] bytes = requestBody.readAllBytes();
                    paramsO.add(simpleTypeParam(param, bytes));
                } else {
                    throw new RuntimeException("文本协议  只能解释成基础数据类型包括String类型 或者是 inputstream类型 其他类型  请使用json协议");
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }

    private boolean doOldFORM(HttpExchange ex) throws Throwable {
        Headers requestHeaders = ex.getRequestHeaders();
        String ContentType = requestHeaders.getFirst(contentType);
        if (ContentType.equals(APPLICATION_FORM_URLENCODED_VALUE)) {
            String path = ex.getRequestURI().getPath();
            PathBean pathBean = vllCache.get(path);
            String httpMethod = ex.getRequestMethod();
            if (!(ContentType.equals(pathBean.getConsumes()) && httpMethod.equals(pathBean.getHttpMethod()))) {
                error405( ex);
                return true;
            }
            Type[] parameterTypes = pathBean.getTypes();
            InputStream requestBody = ex.getRequestBody();
            List<Object> paramsO = new ArrayList<>();
            for (Type param : parameterTypes) {
                if (param.equals(headersClass)) {
                    paramsO.add(requestHeaders);
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else if (param.equals(mapClass)) {
                    byte[] bytes = requestBody.readAllBytes();
                    paramsO.add(queryStringDecoder(new String(bytes),false));
                } else {
                    throw new RuntimeException("application/x-www-form-urlencoded post 协议不能上传文件  只有Map类型接收URL编码参数  ");
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }

    private boolean doMultipart(HttpExchange ex) throws Throwable {
        Headers requestHeaders = ex.getRequestHeaders();
        String ContentType = requestHeaders.getFirst(contentType);
        if (ContentType.startsWith("multipart")) {
            String[] split = ContentType.split(";");
            String boundary = split[1].split("=")[1];
            String path = ex.getRequestURI().getPath();
            PathBean pathBean = vllCache.get(path);
            String httpMethod = ex.getRequestMethod();
            if (!(split[0].equals(pathBean.getConsumes()) && httpMethod.equals(pathBean.getHttpMethod()))) {
                error405( ex);
                return true;
            }
            Type[] parameterTypes = pathBean.getTypes();
            List<Object> paramsO = new ArrayList<>();
            InputStream requestBody = ex.getRequestBody();
            for (Type param : parameterTypes) {
                if (param.equals(headersClass)) {
                    paramsO.add(requestHeaders);
                } else if (param.equals(exchangeClass)) {
                    paramsO.add(ex);
                } else if (param.equals(contextClass)) {
                    paramsO.add(ex.getHttpContext());
                } else if (param.equals(bodyClass)) {
                    paramsO.add(requestBody);
                } else if (param.equals(mapClass)) {
                    paramsO.add(MultipartInput.getInstance(requestBody, boundary).run());
                } else {
                    throw new RuntimeException("设置好文件存放位置  文件已经自动保存");
                }
            }
            Object o = pathBean.getMethodHandle().invokeWithArguments(paramsO);
            success200(ex,pathBean.getProduces(),o);
            return true;
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    private Map<String,Object>  queryStringDecoder(String query,boolean isDecode){
        Map<String, Object> params = null;
        if (query != null) {
            if(isDecode){
                String[] split = query.split("&");
                params = new HashMap<>();
                for (String ss : split) {
                    String[] split1 = ss.split("=");
                    Object v = params.get(split1[0]);
                    if (v!=null) {
                        if(v instanceof String){
                            List<String> list = new ArrayList<>(3);
                            list.add((String) params.get(split1[0]));
                            list.add(split1[1]);
                            params.put(split1[0],list);
                        }else{
                            ((ArrayList<String>)v).add(split1[1]);
                        }
                    }
                }
            }else{
                String[] pairs = query.split("&");
                params = new HashMap<>();
                for (String pair : pairs) {
                    int idx = pair.indexOf("=");
                    if (idx != -1) {
                        String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
                        String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
                        Object v = params.get(key);
                        if (v != null) {
                            if (v instanceof String) {
                                List<String> list = new ArrayList<>(3);
                                list.add((String) params.get(key));
                                list.add(value);
                                params.put(key, list);
                            } else {
                                ((ArrayList<String>) v).add(value);
                            }
                        }
                    }
                }
            }
        }
        return params;
    }

    private void error405(HttpExchange ex) throws IOException {
        ex.getResponseHeaders().set(contentType, TEXT_PLAIN_VALUE+charset);
        ex.sendResponseHeaders(405, error405.length);
        OutputStream responseBody = ex.getResponseBody();
        responseBody.write(error405);
        responseBody.flush();
        responseBody.close();
    }

    private void success200(HttpExchange ex,String type,Object result) throws IOException {
        OutputStream responseBody = ex.getResponseBody();
        if(result instanceof byte[] res){
            Headers responseHeaders = ex.getResponseHeaders();
            responseHeaders.set(contentType, type);
            ex.sendResponseHeaders(200, res.length);
            responseBody.write(res);
            responseBody.flush();
        }else{
            byte[] jsonBytes = JSON.toJSONBytes(result);
            Headers responseHeaders = ex.getResponseHeaders();
            responseHeaders.set(contentType, type);
            ex.sendResponseHeaders(200, jsonBytes.length);
            responseBody.write(jsonBytes);
            responseBody.flush();
        }
    }
}
